#include <QFile>
#include <QTextStream>
#include <QVector>
#include "../../../include/Complex.h"

typedef QVector<Complex> ComplexVector;

void cWrite(const QString& f, const ComplexVector& cmplx);
ComplexVector cRead(const QString& f);

static const QString TBDIR = "../tests/";

int main(int argc, char* argv[])
{
	QTextStream console(stdout);
	console.setCodec("UTF-8");
	console << "~~~~~~~~~~~~~~~~~~~~~~~\n"
			<< "~~ Complex TESTBENCH ~~\n"
			<< "~~~~~~~~~~~~~~~~~~~~~~~\n";
	// An array of complex test numbers. Assumes a length of 100.
	ComplexVector dataset1 = cRead(TBDIR + "dataset1.txt");
	ComplexVector dataset2 = cRead(TBDIR + "dataset2.txt");
	ComplexVector dataset3 = cRead(TBDIR + "dataset3.txt");
	ComplexVector dataset4 = cRead(TBDIR + "dataset4.txt");
	const uint dataset1_size = dataset1.size();
	const uint dataset1_size_2 = dataset1_size / 2;
	// Addition tests.
	ComplexVector addres;
	for (uint i = 0; i < dataset1_size_2; ++i) {
		addres.append(dataset1.at(i) + dataset1.at(i + dataset1_size_2));
	}
	cWrite(TBDIR + "add.cpp.txt", addres);
	// Subtraction tests.
	ComplexVector subres;
	for (uint i = 0; i < dataset1_size_2; ++i) {
		subres.append(dataset1.at(i) - dataset1.at(i + dataset1_size_2));
	}
	cWrite(TBDIR + "sub.cpp.txt", subres);
	// Negation tests.
	ComplexVector negres;
	for (uint i = 0; i < dataset1_size; ++i) {
		negres.append(-dataset1.at(i));
	}
	cWrite(TBDIR + "neg.cpp.txt", negres);
	// Multiplication tests.
	ComplexVector mltres;
	for (uint i = 0; i < dataset1_size_2; ++i) {
		mltres.append(dataset1.at(i) * dataset1.at(i + dataset1_size_2));
	}
	cWrite(TBDIR + "mlt.cpp.txt", mltres);
	// Division tests.
	ComplexVector divres;
	for (uint i = 0; i < dataset1_size_2; ++i) {
		divres.append(dataset1.at(i) / dataset1.at(i + dataset1_size_2));
	}
	cWrite(TBDIR + "div.cpp.txt", divres);
	// Inversion tests.
	ComplexVector invres;
	for (uint i = 0; i < dataset1_size; ++i) {
		invres.append(dataset1.at(i).inverse());
	}
	cWrite(TBDIR + "inv.cpp.txt", invres);
	// Exponentiation (pow) tests.
	ComplexVector powres;
	for (uint i = 0; i < dataset1_size_2; ++i) {
		powres.append(dataset1.at(i).pow(dataset1.at(i + dataset1_size_2)));
	}
	cWrite(TBDIR + "pow.cpp.txt", powres);
	// Absolute value tests.
	ComplexVector absres;
	for (uint i = 0; i < dataset1_size; ++i) {
		absres.append(Complex(abs(dataset1.at(i))));
	}
	cWrite(TBDIR + "abs.cpp.txt", absres);
	// Argument tests.
	ComplexVector argres;
	for (uint i = 0; i < dataset1_size; ++i) {
		argres.append(Complex(arg(dataset1.at(i))));
	}
	cWrite(TBDIR + "arg.cpp.txt", argres);
	// Real part tests.
	ComplexVector realres;
	for (uint i = 0; i < dataset1_size; ++i) {
		realres.append(Complex(real(dataset1.at(i))));
	}
	cWrite(TBDIR + "real.cpp.txt", realres);
	// Imaginary part tests.
	ComplexVector imagres;
	for (uint i = 0; i < dataset1_size; ++i) {
		imagres.append(Complex(imag(dataset1.at(i))));
	}
	cWrite(TBDIR + "imag.cpp.txt", imagres);
	// Square root tests.
	ComplexVector sqrtres;
	for (uint i = 0; i < dataset1_size; ++i) {
		sqrtres.append(sqrt(dataset1.at(i)));
	}
	cWrite(TBDIR + "sqrt.cpp.txt", sqrtres);
	// Natural logarithm tests.
	ComplexVector lnres;
	for (uint i = 0; i < dataset1_size; ++i) {
		lnres.append(ln(dataset1.at(i)));
	}
	cWrite(TBDIR + "ln.cpp.txt", lnres);
	// Natural expoonentiation tests.
	ComplexVector expres;
	for (uint i = 0; i < dataset1_size; ++i) {
		expres.append(exp(dataset1.at(i)));
	}
	cWrite(TBDIR + "exp.cpp.txt", expres);
	// Base 10 logarithm tests.
	ComplexVector log10res;
	for (uint i = 0; i < dataset1_size; ++i) {
		log10res.append(log10(dataset1.at(i)));
	}
	cWrite(TBDIR + "log10.cpp.txt", log10res);
	// Base X logarithm tests.
	ComplexVector logxres;
	for (uint i = 0; i < dataset1_size; i += 2) {
		logxres.append(logx(dataset1.at(i), dataset1.at(i + 1)));
	}
	cWrite(TBDIR + "logx.cpp.txt", logxres);

	// Sine tests.
	ComplexVector sinres, sindres;
	for (uint i = 0; i < dataset1_size; ++i) {
		sinres.append(sin(dataset1.at(i)));
		sindres.append(sind(dataset2.at(i)));
	}
	cWrite(TBDIR + "sin.cpp.txt", sinres);
	cWrite(TBDIR + "sind.cpp.txt", sindres);
	// Cosine tests.
	ComplexVector cosres, cosdres;
	for (uint i = 0; i < dataset1_size; ++i) {
		cosres.append(cos(dataset1.at(i)));
		cosdres.append(cosd(dataset2.at(i)));
	}
	cWrite(TBDIR + "cos.cpp.txt", cosres);
	cWrite(TBDIR + "cosd.cpp.txt", cosdres);
	// Tangent tests.
	ComplexVector tanres, tandres;
	for (uint i = 0; i < dataset1_size; ++i) {
		tanres.append(tan(dataset1.at(i)));
		tandres.append(tand(dataset2.at(i)));
	}
	cWrite(TBDIR + "tan.cpp.txt", tanres);
	cWrite(TBDIR + "tand.cpp.txt", tandres);
	// Secant tests.
	ComplexVector secres, secdres;
	for (uint i = 0; i < dataset1_size; ++i) {
		secres.append(sec(dataset1.at(i)));
		secdres.append(secd(dataset2.at(i)));
	}
	cWrite(TBDIR + "sec.cpp.txt", secres);
	cWrite(TBDIR + "secd.cpp.txt", secdres);
	// Cosecant tests.
	ComplexVector cscres, cscdres;
	for (uint i = 0; i < dataset1_size; ++i) {
		cscres.append(csc(dataset1.at(i)));
		cscdres.append(cscd(dataset2.at(i)));
	}
	cWrite(TBDIR + "csc.cpp.txt", cscres);
	cWrite(TBDIR + "cscd.cpp.txt", cscdres);
	// Cotangent tests.
	ComplexVector cotres, cotdres;
	for (uint i = 0; i < dataset1_size; ++i) {
		cotres.append(cot(dataset1.at(i)));
		cotdres.append(cotd(dataset2.at(i)));
	}
	cWrite(TBDIR + "cot.cpp.txt", cotres);
	cWrite(TBDIR + "cotd.cpp.txt", cotdres);
	// Sinc tests.
	ComplexVector sincres;
	for (uint i = 0; i < dataset1_size; ++i) {
		sincres.append(sinc(dataset1.at(i)));
	}
	cWrite(TBDIR + "sinc.cpp.txt", sincres);
	// Normalised sinc tests.
	ComplexVector sincnres;
	for (uint i = 0; i < dataset1_size; ++i) {
		sincnres.append(sincn(dataset1.at(i)));
	}
	cWrite(TBDIR + "sincn.cpp.txt", sincnres);
	// ArcSine tests.
	ComplexVector asinres, asindres;
	for (uint i = 0; i < dataset1_size; ++i) {
		asinres.append(asin(dataset1.at(i)));
		asindres.append(asind(dataset2.at(i)));
	}
	cWrite(TBDIR + "asin.cpp.txt", asinres);
	cWrite(TBDIR + "asind.cpp.txt", asindres);
	// ArcCosine tests.
	ComplexVector acosres, acosdres;
	for (uint i = 0; i < dataset1_size; ++i) {
		acosres.append(acos(dataset1.at(i)));
		acosdres.append(acosd(dataset2.at(i)));
	}
	cWrite(TBDIR + "acos.cpp.txt", acosres);
	cWrite(TBDIR + "acosd.cpp.txt", acosdres);
	// ArcTangent tests.
	ComplexVector atanres, atandres;
	for (uint i = 0; i < dataset1_size; ++i) {
		atanres.append(atan(dataset1.at(i)));
		atandres.append(atand(dataset3.at(i)));
	}
	cWrite(TBDIR + "atan.cpp.txt", atanres);
	cWrite(TBDIR + "atand.cpp.txt", atandres);
	// ArcSecant tests.
	ComplexVector asecres, asecdres;
	for (uint i = 0; i < dataset1_size; ++i) {
		asecres.append(asec(dataset1.at(i)));
		asecdres.append(asecd(dataset4.at(i)));
	}
	cWrite(TBDIR + "asec.cpp.txt", asecres);
	cWrite(TBDIR + "asecd.cpp.txt", asecdres);
	// ArcCosecant tests.
	ComplexVector acscres, acscdres;
	for (uint i = 0; i < dataset1_size; ++i) {
		acscres.append(acsc(dataset1.at(i)));
		acscdres.append(acscd(dataset4.at(i)));
	}
	cWrite(TBDIR + "acsc.cpp.txt", acscres);
	cWrite(TBDIR + "acscd.cpp.txt", acscdres);
	// ArcCotangent tests.
	ComplexVector acotres, acotdres;
	for (uint i = 0; i < dataset1_size; ++i) {
		acotres.append(acot(dataset1.at(i)));
		acotdres.append(acotd(dataset3.at(i)));
	}
	cWrite(TBDIR + "acot.cpp.txt", acotres);
	cWrite(TBDIR + "acotd.cpp.txt", acotdres);

	// Hyperbolic Sine tests.
	ComplexVector sinhres;
	for (uint i = 0; i < dataset1_size; ++i) {
		sinhres.append(sinh(dataset1.at(i)));
	}
	cWrite(TBDIR + "sinh.cpp.txt", sinhres);
	// Hyperbolic Cosine tests.
	ComplexVector coshres;
	for (uint i = 0; i < dataset1_size; ++i) {
		coshres.append(cosh(dataset1.at(i)));
	}
	cWrite(TBDIR + "cosh.cpp.txt", coshres);
	// Hyperbolic Tangent tests.
	ComplexVector tanhres;
	for (uint i = 0; i < dataset1_size; ++i) {
		tanhres.append(tanh(dataset1.at(i)));
	}
	cWrite(TBDIR + "tanh.cpp.txt", tanhres);
	// Hyperbolic Secant tests.
	ComplexVector sechres;
	for (uint i = 0; i < dataset1_size; ++i) {
		sechres.append(sech(dataset1.at(i)));
	}
	cWrite(TBDIR + "sech.cpp.txt", sechres);
	// Hyperbolic Cosecant tests.
	ComplexVector cschres;
	for (uint i = 0; i < dataset1_size; ++i) {
		cschres.append(csch(dataset1.at(i)));
	}
	cWrite(TBDIR + "csch.cpp.txt", cschres);
	// Hyperbolic Cotangent tests.
	ComplexVector cothres;
	for (uint i = 0; i < dataset1_size; ++i) {
		cothres.append(coth(dataset1.at(i)));
	}
	cWrite(TBDIR + "coth.cpp.txt", cothres);
	// Hyperbolic ArcSine tests.
	ComplexVector asinhres;
	for (uint i = 0; i < dataset1_size; ++i) {
		asinhres.append(asinh(dataset1.at(i)));
	}
	cWrite(TBDIR + "asinh.cpp.txt", asinhres);
	// Hyperbolic ArcCosine tests.
	ComplexVector acoshres;
	for (uint i = 0; i < dataset1_size; ++i) {
		acoshres.append(acosh(dataset1.at(i)));
	}
	cWrite(TBDIR + "acosh.cpp.txt", acoshres);
	// Hyperbolic ArcTangent tests.
	ComplexVector atanhres;
	for (uint i = 0; i < dataset1_size; ++i) {
		atanhres.append(atanh(dataset1.at(i)));
	}
	cWrite(TBDIR + "atanh.cpp.txt", atanhres);
	// Hyperbolic ArcSecant tests.
	ComplexVector asechres;
	for (uint i = 0; i < dataset1_size; ++i) {
		asechres.append(asech(dataset1.at(i)));
	}
	cWrite(TBDIR + "asech.cpp.txt", asechres);
	// Hyperbolic ArcCosecant tests.
	ComplexVector acschres;
	for (uint i = 0; i < dataset1_size; ++i) {
		acschres.append(acsch(dataset1.at(i)));
	}
	cWrite(TBDIR + "acsch.cpp.txt", acschres);
	// Hyperbolic ArcCotangent tests.
	ComplexVector acothres;
	for (uint i = 0; i < dataset1_size; ++i) {
		acothres.append(acoth(dataset1.at(i)));
	}
	cWrite(TBDIR + "acoth.cpp.txt", acothres);

	return 0;
}

void cWrite(const QString& f, const ComplexVector& cmplx)
{
	QFile outfile(f);
	if (outfile.open(QFile::WriteOnly)) {
		QTextStream out(&outfile);
		for (int i = 0; i < cmplx.size(); ++i) {
			QString qs("(%1, %2)\n");
			out << qs.arg(static_cast<double>(cmplx[i].real()), 20, 'e', 14) \
					 .arg(static_cast<double>(cmplx[i].imag()), 20, 'e', 14);
		}
		outfile.close();
	}
}

ComplexVector cRead(const QString& f)
{
	QFile infile(f);
	ComplexVector cmplx;
	if (infile.open(QFile::ReadOnly)) {
		QTextStream in(&infile);
		while (not in.atEnd()) {
			QString ob, cb, comma;
			double x, y;
			in >> ob >> x >> comma >> y >> cb;
			if (ob == "(" and comma == "," and cb == ")") {
				cmplx.append(Complex(static_cast<Complex::RealType>(x),
									 static_cast<Complex::RealType>(y)));
			}
		}
		infile.close();
	}
	return cmplx;
}
